package it.mozzicato.apkwizard;

import it.mozzicato.apkwizard.utils.*;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.logging.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.*;
import javax.swing.text.html.*;

import org.apache.commons.lang.*;

import brut.androlib.*;
import brut.common.*;
import brut.util.*;

public class ApkWizard extends JFrame {
	public static final String SUBPATH_DECOMP = "decomp";
	public static final String SUBPATH_BUILD = "build";
	public static final String SUBPATH_ORIGINAL = "orig";
	public static final String SUBPATH_BACKUP = "backup";
	public static final String ROLLBACK_SUFFIX = "$ROLLBACK";
	public static final String[] SUBPATHS;
	
	private static final String PROP_MAIN_WIND_POSITION = "mainWindowPos";
	//private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss");
	
	static {
		SUBPATHS = new String[] {SUBPATH_DECOMP, SUBPATH_BUILD, SUBPATH_ORIGINAL, SUBPATH_BACKUP};
	}
	
	private static ApkWizard mainWindow;
	private ApkToolLogHandler apkToolLogHandler;
	private JSplitPane splitPane;
	private JList lstProjects;
	private JEditorPane textPane;
	private DefaultListModel lstProjectsModel = new DefaultListModel();
	private JMenu mnuProject;
	private JPopupMenu mnuProjectPopup;
	private FilteredStream systemErrorHandler;

	public ApkWizard() {
		super("Apk Wizard");

		setupLogging(false);
		
		init();
		
		setupGui();

		setSize(600, 600);
		int[] pos = Configuration.getPropertyAsIntArray(PROP_MAIN_WIND_POSITION);
		if(pos != null) {
			if(pos.length >= 2)
				setLocation(pos[0], pos[1]);

			if(pos.length >= 4)
				setSize(pos[2], pos[3]);
		}
		
		setVisible(true);
	}

	private void setupGui() {
		// Add JMenu bar
		JMenuBar menuBar = new JMenuBar();
		// Name the JMenu & Add Items
		JMenu menu = new JMenu("File");
		FileMenu fileMenu = new FileMenu();
		fileMenu.mnuOpen = new JMenuItem("Import Apk...", getIcon("import16"));
		menu.add(fileMenu.mnuOpen);
		menu.addSeparator();
		fileMenu.mnuExit = new JMenuItem("Exit", getIcon("close16"));
		menu.add(fileMenu.mnuExit);
		menuBar.add(menu);

		mnuProject = new JMenu("Project");
		ProjectMenu projectMenu = createProjectMenu(mnuProject.getPopupMenu());
		menuBar.add(mnuProject);
		mnuProject.setEnabled(false);
		
		mnuProjectPopup = new JPopupMenu();
		ProjectMenu projectPopupMenu = createProjectMenu(mnuProjectPopup);

		menu = new JMenu("Tools");
		ToolsMenu toolsMenu = new ToolsMenu();
		toolsMenu.mnuOptions = new JMenuItem("Options...", getIcon("settings16"));
		menu.add(toolsMenu.mnuOptions);
		menuBar.add(menu);

		setJMenuBar(menuBar);

		getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.X_AXIS));

		splitPane = new JSplitPane();
		getContentPane().add(splitPane);

		JPanel leftPanel = new JPanel();
		leftPanel.setLayout(new BorderLayout());
		
		lstProjects = new JList(lstProjectsModel);
		lstProjects.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		JScrollPane lstProjectsScroll = new JScrollPane(lstProjects, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		leftPanel.add(new JLabel("Projects"), BorderLayout.NORTH);
		leftPanel.add(lstProjectsScroll, BorderLayout.CENTER);		
		splitPane.setLeftComponent(leftPanel);

		textPane = new JEditorPane("text/html", "<html><body id=\"bodyElement\"></body></html>");
		splitPane.setRightComponent(
				new JScrollPane(textPane, 
						JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
						JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));
		
		// Setup logging handlers
		apkToolLogHandler.setTextArea(textPane);
		systemErrorHandler.setTextArea(textPane);
		
		createHandlers(fileMenu, toolsMenu);
		createProjectMenuHandlers(projectMenu);
		createProjectMenuHandlers(projectPopupMenu);
	}

	private ProjectMenu createProjectMenu(JPopupMenu popupMenu) {
		ProjectMenu ret = new ProjectMenu();
		ret.mnuDecompile = new JMenuItem("Decompile", getIcon("decompile16"));
		popupMenu.add(ret.mnuDecompile);
		popupMenu.addSeparator();
		ret.mnuBuild = new JMenuItem("Build All", getIcon("build16"));
		popupMenu.add(ret.mnuBuild);
		ret.mnuBuild = new JMenuItem("Build only classes", getIcon("build16"));
		popupMenu.add(ret.mnuBuild);
		ret.mnuBuild = new JMenuItem("Build only resources", getIcon("build16"));
		popupMenu.add(ret.mnuBuild);
		ret.mnuSign = new JMenuItem("Sign Apk", getIcon("sign16"));
		popupMenu.add(ret.mnuSign);
		ret.mnuZipalign = new JMenuItem("Zipalign Apk", getIcon("zipalign16"));
		popupMenu.add(ret.mnuZipalign);
		popupMenu.addSeparator();
		ret.mnuBuildAndSignAndZipalign = new JMenuItem("Build All & Sign & Zipalign Apk", getIcon("buildComplete16"));
		popupMenu.add(ret.mnuBuildAndSignAndZipalign);
		popupMenu.addSeparator();
		ret.mnuResEditor = new JMenuItem("Resource Editor...", getIcon("tools16"));
		popupMenu.add(ret.mnuResEditor);
		popupMenu.addSeparator();
		JMenu mnuUtils = new JMenu("Utilities");
		mnuUtils.setIcon(getIcon("utilities16"));
		ret.mnuProjectDir = new JMenuItem("Go to project dir...");
		ret.mnuSourceDir = new JMenuItem("Go to source dir...");
		ret.mnuResDir = new JMenuItem("Go to resource dir...");
		mnuUtils.add(ret.mnuProjectDir);
		mnuUtils.add(ret.mnuSourceDir);
		mnuUtils.add(ret.mnuResDir);
		popupMenu.add(mnuUtils);
		
		return ret;
	}

	public static Icon getIcon(String icon) {
		return new ImageIcon(ResourceDialog.class.getClassLoader().getResource("it/mozzicato/apkwizard/resources/images/" + icon + "-icon.png"));
	}

	private void init() {
		UIManager.put("FileChooser.readOnly", Boolean.TRUE);
		
		File projDir = PathUtils.getFileInProjects((String[])null);
		if(projDir.exists()) {
			File[] listFiles = projDir.listFiles();
			for (File file : listFiles) {
				if(file.isDirectory())
					lstProjectsModel.addElement(file.getName());
			}
		}
	}

	public void createHandlers(FileMenu fileMenu, ToolsMenu toolsMenu) {
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				Configuration.setProperty(PROP_MAIN_WIND_POSITION, getX() + "," + getY() + "," + getWidth() + "," + getHeight());
				System.exit(0);
			}
		});

		fileMenu.mnuOpen.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				importApk();
			}
		});

		fileMenu.mnuExit.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				System.exit(0);
			}
		});
		
		lstProjects.addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				mnuProject.setEnabled(lstProjects.getSelectedIndex() >= 0);
			}
		});
		
		lstProjects.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				if(e.getButton()==MouseEvent.BUTTON3)
					lstProjects.setSelectedIndex(lstProjects.locationToIndex(e.getPoint()));				
				mouseReleased(e);
			}
			
			@Override
			public void mouseReleased(MouseEvent e) {
				if(e.isPopupTrigger() && lstProjects.getSelectedIndex() >= 0) {
					mnuProjectPopup.show(lstProjects, e.getX(), e.getY()+5);
				}
			}
		});
	}

	public void createProjectMenuHandlers(ProjectMenu projectMenu) {
		projectMenu.mnuDecompile.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				decompile();
			}
		});
		
		projectMenu.mnuBuild.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				build();
			}
		});

		projectMenu.mnuSign.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				sign();
			}
		});

		projectMenu.mnuZipalign.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				zipalign();
			}
		});

		projectMenu.mnuBuildAndSignAndZipalign.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				buildAndSignAndZipalign();
			}
		});

		projectMenu.mnuResEditor.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				String projName = (String) lstProjects.getSelectedValue();
				
				File resourceDir = PathUtils.getFileInProjects(projName, ApkWizard.SUBPATH_DECOMP, "res");
				if(!resourceDir.exists())
					JOptionPane.showMessageDialog(ApkWizard.this, "There is no resource directory for this project.\nAre you sure you have decompiled it first?", "Error", JOptionPane.ERROR_MESSAGE);
				else {
					ResourceDialog resourceDialog = new ResourceDialog(ApkWizard.this, projName);
					resourceDialog.setVisible(true);
				}
			}
		});
		
		projectMenu.mnuProjectDir.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				File projDir = PathUtils.getFileInProjects((String) lstProjects.getSelectedValue());
				if(!projDir.exists()) {
					JOptionPane.showMessageDialog(ApkWizard.this, "Ooops! Unable to find the project directory! That should not happen...", "Error", JOptionPane.ERROR_MESSAGE);
				}
				else {
					try {
						Desktop.getDesktop().open(projDir);
					}
					catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		});

		projectMenu.mnuSourceDir.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				File smaliDir = PathUtils.getFileInProjects((String) lstProjects.getSelectedValue(), SUBPATH_DECOMP, "smali");
				if(!smaliDir.exists()) {
					JOptionPane.showMessageDialog(ApkWizard.this, "Unable to find the smali directory: did you decompile the project?", "Error", JOptionPane.ERROR_MESSAGE);
				}
				else {
					try {
						Desktop.getDesktop().open(smaliDir);
					}
					catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		});

		projectMenu.mnuResDir.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				File resDir = PathUtils.getFileInProjects((String) lstProjects.getSelectedValue(), SUBPATH_DECOMP, "res");
				if(!resDir.exists()) {
					JOptionPane.showMessageDialog(ApkWizard.this, "Unable to find the resource directory: did you unpacked the project?", "Error", JOptionPane.ERROR_MESSAGE);
				}
				else {
					try {
						Desktop.getDesktop().open(resDir);
					}
					catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		});
	}

	protected void decompile() {
		if(lstProjects.getSelectedIndex() >= 0) {
			String projName = (String) lstProjects.getSelectedValue();
			final File inputFile = PathUtils.getFileInProjects(projName, SUBPATH_ORIGINAL, projName);
			if(!inputFile.exists()) {
				JOptionPane.showConfirmDialog(this, String.format("Unable to find the original file '%s' in directory '%s'.", inputFile.getName(), inputFile.getParent()), "Error", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			final File destDir = PathUtils.getFileInProjects(projName, SUBPATH_DECOMP);
			if(destDir.exists()) {
				int result = JOptionPane.showConfirmDialog(this, "This project seems to be already decompiled.\n" +
						"By continuing, the 'decomp' folder will be recreated, so all the changes in that directory will be lost.\n" +
						"Are you sure you want to continue decompilation?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
				if(result != JOptionPane.YES_OPTION) {
					return;
				}
			}
			/*final JDialog dlg = new JDialog(this, "Decompiling...", true);
			JProgressBar dpb = new JProgressBar(0, 500);
			dpb.setIndeterminate(true);
			dlg.add(BorderLayout.SOUTH, dpb);
			final JLabel lblInfo = new JLabel("Starting decompilation...");
			dlg.add(BorderLayout.CENTER, lblInfo);
			dlg.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
			dlg.setSize(300, 75);
			dlg.setLocationRelativeTo(this);

			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					dlg.setVisible(true);
				}
			});
			t.start();
			*/
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					appendLog(textPane, "\n---------- Starting Decompilation -----------\n", Level.INFO);
					try {
						ApkDecoder apkdecoder = new ApkDecoder();
						apkdecoder.setApkFile(inputFile);
						apkdecoder.setOutDir(destDir);
						apkdecoder.setForceDelete(true);
						//apkdecoder.setDebugMode(true);
						apkdecoder.decode();
						appendLog(textPane, "---------- Decompilation Completed Succesfully-----------\n", Level.INFO);
					} catch (Exception e) {
						appendLog(textPane, stackTraceToString(e), Level.SEVERE);
						appendLog(textPane, "---------- Decompilation Completed WITH ERRORS-----------\n", Level.SEVERE);
					}
				}
			});
			t.start();

		}
	}

	protected void buildAndSignAndZipalign() {
		build();
	}

	protected Thread build() {
		if(lstProjects.getSelectedIndex() >= 0) {
			String projName = (String) lstProjects.getSelectedValue();
			final File destDir = PathUtils.getFileInProjects(projName, SUBPATH_BUILD);
			if(!destDir.exists())
				destDir.mkdir();
			
			final File sourceDir = PathUtils.getFileInProjects(projName, SUBPATH_DECOMP);
			final File outputFile = PathUtils.getFileInProjects(projName, SUBPATH_BUILD, "unsigned-" + projName 
				//+ '_' + sdf.format(new Date())
			);
			if(outputFile.exists()) {
				if(JOptionPane.showConfirmDialog(this, 
					String.format("Found another built file '%s' in directory '%s'.\nContinuing it will be replaced\nAre you sure you want to continue?", 
					outputFile.getName(), outputFile.getParent()), "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
					return null;
				
				outputFile.delete();
			}
			

			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					appendLog(textPane, "\n---------- Starting Building -----------\n", Level.INFO);
					try {
						new Androlib().build(sourceDir, outputFile, true, false); 
						appendLog(textPane, "---------- Build Completed Successfully -----------\n", Level.INFO);
					} catch (Exception e) {
						appendLog(textPane, stackTraceToString(e), Level.SEVERE);
						appendLog(textPane, "---------- Build Completed WITH ERRORS -----------\n", Level.SEVERE);
					}
				}
			});
			t.start();

			return t;
		}
		
		return null;
	}
	
	private Thread sign() {
		if(lstProjects.getSelectedIndex() >= 0) {
			String projName = (String) lstProjects.getSelectedValue();
			final File inputFile = PathUtils.getFileInProjects(projName, SUBPATH_BUILD, "unsigned-" + projName);
			if(!inputFile.exists()) {
				JOptionPane.showConfirmDialog(this, String.format("Unable to find the unsigned built file '%s' in directory '%s'.", inputFile.getName(), inputFile.getParent()), "Error", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
				return null;				
			}
			
			final File outputFile = PathUtils.getFileInProjects(projName, SUBPATH_BUILD, "signed-" + projName);
			if(outputFile.exists()) {
				if(JOptionPane.showConfirmDialog(this, 
					String.format("Found another built file '%s' in directory '%s'.\nContinuing it will be replaced\nAre you sure you want to continue?", 
					outputFile.getName(), outputFile.getParent()), "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
					return null;
				
				outputFile.delete();
			}

			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					appendLog(textPane, "\n---------- Starting Signing -----------\n", Level.INFO);
					try {
						ClassLoader classLoader = ResourceDialog.class.getClassLoader();
						SignApk.sign(
							new File(classLoader.getResource("it/mozzicato/apkwizard/resources/testkey.x509.pem").getPath()), 
							new File(classLoader.getResource("it/mozzicato/apkwizard/resources/testkey.pk8").getPath()),
							inputFile,
							outputFile, 
							true);
						appendLog(textPane, "---------- Package Successfully Signed -----------\n", Level.INFO);
					} catch (Exception e) {
						appendLog(textPane, stackTraceToString(e), Level.SEVERE);
						appendLog(textPane, "---------- ERRORS during Package Signing -----------\n", Level.SEVERE);
					}
				}
			});
			t.start();

			return t;
		}
			
		// signapk.jar -w testkey.x509.pem testkey.pk8 "$INFILE" "$OUTFILE"
		return null;
	}

	private Thread zipalign() {
		if(lstProjects.getSelectedIndex() >= 0) {
			String projName = (String) lstProjects.getSelectedValue();
			final File inputFile = PathUtils.getFileInProjects(projName, SUBPATH_BUILD, "signed-" + projName);
			if(!inputFile.exists()) {
				JOptionPane.showConfirmDialog(this, String.format("Unable to find the signed built file '%s' in directory '%s'.", inputFile.getName(), inputFile.getParent()), "Error", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
				return null;				
			}
			
			final File outputFile = PathUtils.getFileInProjects(projName, SUBPATH_BUILD, projName);
			if(outputFile.exists()) {
				if(JOptionPane.showConfirmDialog(this, 
					String.format("Found another zipaligned built file '%s' in directory '%s'.\nContinuing it will be replaced\nAre you sure you want to continue?", 
					outputFile.getName(), outputFile.getParent()), "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
					return null;
				
				outputFile.delete();
			}

			appendLog(textPane, "\n---------- Starting Zipaligning Package -----------\n", Level.INFO);
			try {
				OS.exec(new String[] {
				   "zipalign",
				   "-fv",
				   "4",
				   inputFile.getAbsolutePath(),
				   outputFile.getAbsolutePath()
				});
				appendLog(textPane, "---------- Package Successfully Zipaligned -----------\n", Level.INFO);
			} catch (BrutException e) {
				appendLog(textPane, stackTraceToString(e), Level.SEVERE);
				appendLog(textPane, "---------- ERRORS during Package Zipaligning -----------\n", Level.SEVERE);
			}
		}
		// zipalign -fv 4 "place-apk-here-for-modding/repackaged-$STRING.apk" "place-apk-here-for-modding/repackaged-$STRING-aligned.apk"
		return null;
	}

	protected void importApk() {
		JFileChooser fc = new JFileChooser(Configuration.getProperty(Configuration.LAST_IMPORT_DIR));

		fc.setFileFilter(new FileFilter() {

			@Override
			public String getDescription() {
				return "*.apk, *.jar";
			}

			@Override
			public boolean accept(File f) {
				return f.isDirectory() || f.getName().endsWith(".apk") || f.getName().endsWith(".jar");
			}
		});

		if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
			File selectedFile = fc.getSelectedFile();
			
			Configuration.setProperty(Configuration.LAST_IMPORT_DIR, selectedFile.getParent());
			createProjectStructure(selectedFile);
		}
	}

	private void createProjectStructure(File selectedFile) {
		String projName = selectedFile.getName();
		/*int pos = projName.lastIndexOf('.');
		if(pos > 0)
			projName = projName.substring(0, pos);*/
		File projDir = PathUtils.getFileInProjects(projName);
		if(!projDir.exists() && !projDir.mkdirs())
			JOptionPane.showMessageDialog(this, "Unable to create project directory!", "Error", JOptionPane.WARNING_MESSAGE);
		else {
			lstProjectsModel.addElement(projDir.getName());
			File destDir = PathUtils.getFileInProjects(projName, SUBPATH_ORIGINAL);
			if(!destDir.exists()) 
				destDir.mkdirs();
			File dest = PathUtils.getFileInProjects(projName, SUBPATH_ORIGINAL, selectedFile.getName());
			
			try {
				PathUtils.copyFile(selectedFile, dest);
			}
			catch (IOException e) {
				JOptionPane.showMessageDialog(this, "Unable to oopy file\n" + selectedFile.getAbsolutePath() + "\nto\n" + dest.getAbsolutePath());
			}
		}
	}
	
	public static void main(String[] s) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		
		mainWindow = new ApkWizard();
	}
	
	private void setupLogging(boolean flag) {
		// Capture apktool logging to textarea
		Logger logger = Logger.getLogger("");
		ConsoleHandler consolehandler = new ConsoleHandler();
		logger.removeHandler(logger.getHandlers()[0]);
		logger.addHandler(consolehandler);
		if (flag) {
			consolehandler.setLevel(Level.ALL);
			logger.setLevel(Level.ALL);
		}
		else {
			consolehandler.setFormatter(new Formatter() {

				@Override
				public String format(LogRecord logrecord) {
					return (new StringBuilder()).append(logrecord.getLevel().toString().charAt(0)).append(": ").append(logrecord.getMessage()).append(
						System.getProperty("line.separator")).toString();
				}

			});
		}
		
		apkToolLogHandler = new ApkToolLogHandler();
		logger.addHandler(apkToolLogHandler);
		
		// Capture generic System.error to textarea
		systemErrorHandler = new FilteredStream(new ByteArrayOutputStream(), System.err);
		PrintStream aPrintStream = new PrintStream(systemErrorHandler);
		System.setErr(aPrintStream);
	}

	/**
	 * Creates a string representation of the given exception's stack trace
	 * 
	 * @param e
	 * @return
	 */
	public static String stackTraceToString(Throwable e) {
		if(e != null) {
			CharArrayWriter c = new CharArrayWriter(5000);
			e.printStackTrace(new PrintWriter(c));
			return c.toString();
		}
		
		return "";
	}
	
	public static ApkWizard getMainWindow() {
		return mainWindow;
	}

	public static void appendLog(JTextComponent textPane, String text, Level logLevel) {
		try {
			text = text.replaceAll("\n", "_NEWLINE_");
			if(logLevel==Level.SEVERE)
				text = "<div><font color=\"red\">" + StringEscapeUtils.escapeHtml(text) + "</font></div>";
			else
				text = "<div>" + StringEscapeUtils.escapeHtml(text) + "</div>";
			text = text.replaceAll("_NEWLINE_", "<br />");
			
			HTMLDocument doc = (HTMLDocument) textPane.getDocument();
			Element element = doc.getElement("bodyElement");
			doc.insertBeforeEnd(element, text);
			textPane.setCaretPosition(textPane.getDocument().getLength());
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
}


class FileMenu {
	JMenuItem mnuOpen;
	JMenuItem mnuExit;
}

class ProjectMenu {
	JMenuItem mnuSign;
	JMenuItem mnuResEditor;
	JMenuItem mnuBuild;
	JMenuItem mnuZipalign;
	JMenuItem mnuBuildAndSignAndZipalign;
	JMenuItem mnuDecompile;
	JMenuItem mnuProjectDir;
	JMenuItem mnuSourceDir;
	JMenuItem mnuResDir;
}

class ToolsMenu {
	JMenuItem mnuOptions;
}

class FilteredStream extends FilterOutputStream {
	private JTextComponent textArea;
	private PrintStream systemErr;

	public FilteredStream(OutputStream aStream, PrintStream systemErr) {
		super(aStream);
		this.systemErr = systemErr;
	}

	public void setTextArea(JTextComponent textPane) {
		this.textArea = textPane;
	}
	
	public void write(byte b[]) throws IOException {
		write(b, 0, b.length);
	}

	public void write(byte b[], int off, int len) throws IOException {
		if(systemErr != null)
			systemErr.write(b, off, len);
		
		String aString = new String(b, off, len);
		ApkWizard.appendLog(textArea, aString, Level.SEVERE);
	}
}